package org.examat.web.controladores;

import java.io.Serializable;
import java.util.*;
import javax.annotation.Resource;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import javax.faces.model.SelectItem;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.UserTransaction;
import org.examat.entidades.*;
import org.examat.web.controladores.util.Sesiones;

/**
 *
 * @author Fer
 */
@Named
@SessionScoped
public class ExamenManager
    implements Serializable 
{
    /**
     * Contiene el enunciado de la pregunta actual
     */
    private String enunciadoActual;

    /**
     * Contiene una lista con los incisos a desplegar 
     * en la vista (página xthml)
     */
    private ArrayList<SelectItem> incisosActual;

    /**
     * Indice de la pregunta actual. Es un número en el rango
     * [0, idPreguntas.size() - 1]
     */
    private Integer indiceActual;
    
    /**
     * Objeto de persistencia que representa a la pregunta
     * que esta contestando el alumno
     */
    private Pregunta preguntaActual;
    
    /**
     * El tipo de examen que resolverá el alumno.
     * Valores: 
     * 1 si es el examen fácil
     * 2 si es el examen difícil
     */
    private Integer tipoExamen;
    
    /**
     * Lista que contiene los ID's de las preguntas que
     * responderá el alumno. Estos ID's son seleccionados
     * aleatoriamente
     */
    private ArrayList<Integer> idsPreguntas;
    
    /**
     * Es un ArrayList de 30 elementos
     * Para cada pregunta, donde cada elemento
     * es un ArrayList de 4 elementos que contienen
     * el orden en el que se van a colocar las preguntas
     */
    private ArrayList< ArrayList< Integer> > ordenPreguntas;
    
    /**
     * Contiene un arreglo con el índice del inciso elegido para cada pregunta
     * Que tenga -1 si la dejó vacía y si contestó algo,
     * El índice del inciso que eligió 0 para a), 1 para b) étc
     */
    private ArrayList<Integer> elecciones;
    
    /**
     * Representa cuantas preguntas tiene el objeto actual.
     */
    private Integer numPreguntas;
    
    private Alumno alumno;
    
    private Integer tiempoRestante;
    
    /**
     * Manejador de los objetos de persisitencia
     * (Interfaz con la base de datos)
     */
    @PersistenceContext
    private EntityManager em;
    
    /**
     * Transaccion para insertar en la Base de datos
     */
    @Resource
    private UserTransaction utx;
    
    public Integer getNumPreguntas()
    {
        if(this.getTipoExamen()==1)
            this.numPreguntas =  25;
        else
            this.numPreguntas =  30;
        return this.numPreguntas;
    }
    
    /**
     * Get the value of tipoExamen
     *
     * @return the value of tipoExamen
     */
    public Integer getTipoExamen() 
    {
        Long aux;
        Integer res;
        
        aux = (Long)this.em
                .createNamedQuery("TipoExamen.getTipoExamenAlumno")
                .setParameter(1, (Integer)Sesiones.get("user_claveunica"))
                .getSingleResult();
        res = Integer.valueOf(aux.toString(), 10);
        tipoExamen = res;
        
        return tipoExamen;
    }


    
    /**
     * Get the value of idsPreguntas
     *
     * @return the value of idsPreguntas
     */
    public ArrayList<Integer> getIdsPreguntas() 
    {
        return idsPreguntas;
    }
    
    /**
     * Crea el orden en el que se van a desplegar las opcines
     * hasta ahora está fijo a 30 elementos, pero hay que
     * detectar el tipo de examen para elegir entre 25 y 30
     */
    private void generaOrdenPreguntas()
    {    
        ArrayList<Integer> posibles;
        int idx;
        //TODO: por ahora, el tamaño del examen está hardcodeado
        int tam = this.getNumPreguntas();
        this.idsPreguntas = new ArrayList<Integer>();
        for(int i=1; i<=tam; i++) {
            posibles = this.getPosiblesPreguntas(i, this.tipoExamen);
            idx = (int)(Math.random()*(posibles.size()-1)) ;
            this.idsPreguntas.add(posibles.get(idx));
        }
    }
    
    /**
     * Obtiene una lista con los ID's de las preguntas
     * que pertenezan a la Seccion dada por "idSeccion" y
     * que también pertenezcan al Tipo de Examen dado por 
     * "idTipoExamen".
     * @param idSeccion ID del registro de la sección.
     * @param idTipoExamen ID del registro del Tipo de Examen.
     * @return Un ArrayList de Integers con los ID' de las preguntas
     *  que coincide con los criterios dados.
     */
    public ArrayList<Integer> getPosiblesPreguntas(Integer idSeccion, Integer idTipoExamen)
    {
        List li;
        ArrayList<Integer> res = new ArrayList<Integer>();
        li = this.em
                .createNamedQuery("Pregunta.findBySeccionAndTipoExamen")
                .setParameter(1, idSeccion)
                .setParameter(2, idTipoExamen)
                .getResultList();
        for(Object n: li) {
            res.add(Integer.valueOf(n.toString()));
        }
        return res;
    }
    
    /**
     * Genera un orden aleatorio de los incisos de todas las preguntas
     * a contestar en el examen.
     */
    private void generaOrdenInicisos()
    {
        ordenPreguntas = new ArrayList< ArrayList<Integer> >();
        
        int mark[] = new int[4];
        
        for(int i = 0; i <this.getNumPreguntas(); i ++){
            ArrayList<Integer> preguntaActual = new ArrayList<Integer>();
            
            // Llena mark con ceros
            for(int pox = 0 ; pox < 4 ; pox ++){
                mark[pox] = 0;
            }
            
            // Genera las 4 posiciones aleatorias
            for(int p = 0 ; p < 4; p++){
                int num =  ( (int) (Math.random() * 10) ) % 4;
                while(mark[num] == 1){
                    // Mientras esté marcada, genera otro número
                    num =  ( (int) (Math.random() * 10) ) % 4;
                }
                // Lo marca
                mark[num] = 1;
                preguntaActual.add(num);
            }
            // Agrega el orden creado
            ordenPreguntas.add(preguntaActual);
        }
        
    }
    
    /**
     * Genera elecciones de respuesta de tal manera
     * que las preguntas aparezcan como no contestadas.
     */
    private void generaElecciones()
    {
        this.elecciones = new ArrayList<Integer>();
        for(int i=0; i<this.getNumPreguntas(); i++) 
            this.elecciones.add(-1);
    }
    
    /**
     * Get the value of string
     *
     * @return the value of string
     */
    public ArrayList<SelectItem> getInicisosActual() 
    {
        if(this.preguntaActual!=null && this.ordenPreguntas!=null) {
            this.incisosActual = new ArrayList<SelectItem>();
            ArrayList<Integer> ord = this.ordenPreguntas.get(this.indiceActual);
            SelectItem si;
            for(int i=0; i<ord.size(); i++) {
                switch(ord.get(i)) {
                    case 0:
                        si = new SelectItem(i, (char)((int)'a' +i) +") " +preguntaActual.getOpcion1());
                        break;
                    case 1:
                        si = new SelectItem(i, (char)((int)'a' +i) +") " +preguntaActual.getOpcion2());
                        break;
                    case 2:
                        si = new SelectItem(i, (char)((int)'a' +i) +") " +preguntaActual.getOpcion3());
                        break;
                    case 3:
                        si = new SelectItem(i, (char)((int)'a' +i) +") " +preguntaActual.getOpcion4());
                        break;
                    default:
                        si = null;
                        break;
                }
                this.incisosActual.add(si);
                if(this.getTiempoRestante() <= 0)
                    si.setDisabled(true);
            }
            si = new SelectItem(-1, "e) Dejar en blanco");
            this.incisosActual.add(si);
            if(this.getTiempoRestante() <= 0)
                    si.setDisabled(true);
        }
        return this.incisosActual;
    }

    /**
     * Obtiene el enunciado de la pregunta a mostrar en pantalla.
     * @return Un String con el enunciad de la pregunta actual.
     */
    public String getEnunciadoActual() 
    {
        if(this.preguntaActual!=null) {
            this.enunciadoActual = (this.indiceActual+1) +". " +this.preguntaActual.getEnunciado();
        }
        else {
            this.enunciadoActual = "ERROR: No encontre la pregunta";
        }
        
        return enunciadoActual;
    }
    
    /**
     * Get el orden de las preguntas
     *
     * @return el orden de las preguntas
     */
    public ArrayList<ArrayList<Integer> > getOrden()
    {
        return ordenPreguntas;
    }
    
    public ArrayList<Integer> getEleccion(){
        return elecciones;
    }
    
    /**
     * Inicializa el ExamenManager para que pueda aplicar
     * el examen de un determinado Alumno
     * @return Nombre de la vista (*.xhtml) que desplegará el examen
     */
    public String configuracionInicial()
    {
        List<Examen> examen = this.getExamenGuardado();
        this.getTipoExamen();
        this.generaOrdenInicisos();
        if(examen==null || examen.size()==0) {
            this.generaElecciones();
            this.generaOrdenPreguntas();
            this.guardaExamen();
            this.guardaFechaInicio();
            System.out.println("Examen GENERADO exitosamente para: " + Sesiones.get("user_claveunica"));
            this.setIndicePregunta(0);
        }
        else if(this.numPreguntas==examen.size()) {
            this.cargaExamenGuardado(examen);
            System.out.println("Examen CARGADO exitosamente para: " + Sesiones.get("user_claveunica"));
            this.setIndicePregunta(0);
        }
        else {
            System.err.println("Hay una inconsistencia en la BD para " + Sesiones.get("user_claveunica"));
        }
        
        return "examen";
    }
    
    private void guardaFechaInicio()
    {
        this.getAlumno();
        this.alumno.setFechaInicioExamen(Calendar.getInstance().getTime());
        try {
            this.utx.begin();
            this.em.joinTransaction();
            this.em.merge(this.alumno);
            this.utx.commit();
        }
        catch(Exception e) {
            try {
                System.err.print("No se puede guardar la fecha de inicio del examen. Rollback...");
                this.utx.rollback();
                System.err.println("OK");
                System.err.println(e.getMessage());
            }
            catch(Exception e2) {
                System.err.println("NO");
            }
        }
    }
    
    private void cargaExamenGuardado(List<Examen> examen)
    {
        //cargamos idPreguntas
        Iterator<Examen> it = examen.iterator();
        this.idsPreguntas = new ArrayList<Integer>();
        this.elecciones = new ArrayList<Integer>();
        while(it.hasNext())
            this.idsPreguntas.add(it.next().getExamenPK().getIdPregunta());
        //cargamos las preguntas ya contestadas
        Examen ex;
        int tam = examen.size();
        int j;
        for(int i=0; i<tam; i++) {
            ex = examen.get(i);
            if(ex.getRespuestaAlumno()>=0) {
                j = 0;
                while(this.ordenPreguntas.get(i).get(j)!=ex.getRespuestaAlumno())
                    j++;
                this.elecciones.add(j);
            }
            else if(ex.getRespuestaAlumno()==-1) {
                this.elecciones.add(-1);
            }
            else {
                this.elecciones.add(-1);
            }
        }
    }
    
    private List<Examen> getExamenGuardado()
    {
        Integer cu = (Integer)Sesiones.get("user_claveunica");
        List li = em.createNamedQuery("Examen.findByClaveUnicaAlumno")
                .setParameter("claveUnicaAlumno", cu)
                .getResultList();
        return li;
    }
    
    /**
     * Guarda en la base de datos registros de la tabla
     * Examen, que representan un examen recién generado
     */
    private void guardaExamen()
    {
        Integer cu = (Integer)Sesiones.get("user_claveunica");
        ExamenPK epk;
        Examen exa;
        for(int i=0; i<this.numPreguntas; i++) {
            epk = new ExamenPK(cu, this.idsPreguntas.get(i));
            exa = new Examen(epk);
            exa.setRespuestaAlumno(this.elecciones.get(i));
            exa.setIncisoRespuesta('X');
            try {
                utx.begin();
                em.joinTransaction();
                em.persist(exa);
                utx.commit();
            }
            catch(Exception e) {
                try {
                    utx.rollback();
                }
                catch(Exception ex) {
                    
                }
            }
        }
    }
    
    private void actualizaPregunta(int indice)
    {
        Integer cu = (Integer)Sesiones.get("user_claveunica");
        ExamenPK epk = new ExamenPK(cu, this.idsPreguntas.get(indice));
        Examen exa = em.find(Examen.class, epk);
        if(exa!=null) {
            if(this.elecciones.get(indice)!=null && this.elecciones.get(indice)>=0) {
                exa.setRespuestaAlumno(this.ordenPreguntas.get(indice).get(this.elecciones.get(indice)));
                exa.setIncisoRespuesta((char)(this.elecciones.get(indice) + 'a'));
            }
            else if(this.elecciones.get(indice)!=null) {
                exa.setRespuestaAlumno(this.elecciones.get(indice)); //-1 o -2 dependiendo de lo que hizo
                if(this.elecciones.get(indice)==-1)
                    exa.setIncisoRespuesta('e');
                else
                    exa.setIncisoRespuesta('X');
            }
            else {
                exa.setRespuestaAlumno(-2);
                exa.setIncisoRespuesta('X');
            }
            try {
                utx.begin();
                em.joinTransaction();
                em.merge(exa);
                utx.commit();
                System.out.println("Pregunta Actualizada");
            }
            catch(Exception e) {
                try {
                    utx.rollback();
                }
                catch(Exception ex) {
                    
                }
            }
        }
    }
    
    /**
     * Muestra en pantalla la pregunta siguiente
     */
    public void preguntaAnterior()
    {
        if(this.indiceActual==0)
            this.setIndicePregunta(this.idsPreguntas.size() - 1);
        else
            this.setIndicePregunta(this.indiceActual - 1);
    }
    
    /**
     * Muestra en pantalla la pregunta anterior
     */
    public void preguntaSiguiente()
    {
        if(this.indiceActual==this.idsPreguntas.size()-1)
            this.setIndicePregunta(0);
        else
            this.setIndicePregunta(this.indiceActual + 1);
    }
    
    /**
     * Get the value of indicePregunta
     *
     * @return the value of indicePregunta
     */
    public Integer getIndicePregunta() 
    {
        return indiceActual;
    }

    /**
     * Establece el valor de la propiedad indicePregunta
     *
     * @param indicePregunta new value of indicePregunta
     */
    public void setIndicePregunta(Integer indicePregunta) 
    {
        this.indiceActual = indicePregunta;
        
        this.preguntaActual = this.em.find(Pregunta.class,
                this.idsPreguntas.get(this.indiceActual));
    }
    
    
    public Integer getRespuestaActual()
    {
        return this.elecciones.get(this.indiceActual);
    }
    
    public void setRespuestaActual(Integer respuestaActual)
    {
        this.elecciones.set(this.indiceActual, respuestaActual);
        //metemos en bd
        this.actualizaPregunta(this.indiceActual);
    }
    
    public Alumno getAlumno()
    {
        this.alumno = em.find(Alumno.class, (Integer)Sesiones.get("user_claveunica"));
        
        return this.alumno;
    }
    
    /**
     * Regresa la duración del examen en MINUTOS
     * @return 
     */
    public Integer getDuracionExamen()
    {
        TipoExamen te = em.find(TipoExamen.class, this.getTipoExamen());
        return te.getDuracion();
    }
    
    /**
     * Regresa el tiempo que le queda al estudiante para 
     * resolver el examen en SEGUNDOS
     * @return 
     */
    public Integer getTiempoRestante()
    {
        Date hoy, ini;
                
        hoy = Calendar.getInstance().getTime();
        ini = this.getAlumno().getFechaInicioExamen();
        long diff = (long)this.getDuracionExamen()*60*1000 - (hoy.getTime() - ini.getTime());
        
        return (int)diff/1000;
    }
    
    public String getNombreAlumno()
    {
        return
                this.alumno.getNombre() + " "
                +this.alumno.getAPaterno() + " "
                +this.alumno.getAMaterno();
    }
    
    public boolean isExamenNuevo()
    {
        return this.getAlumno().getFechaInicioExamen() == null;
    }
    
    public boolean isExamenCalificado()
    {
        return this.getAlumno().getCalificacion() != null;
    }
}
